package dtrs.corba.serverSide;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Hashtable;

import org.omg.CORBA.ORB;

import dtrs.corba.server.common.ShowDTO;
import dtrs.corba.server.semaphores.DtrsMapShowID;
import dtrs.corba.server.semaphores.ManageFileSemaphore;
import dtrs.corba.server.semaphores.ManageMapShowSemaphore;
import dtrs.corba.server.semaphores.ReserveFile;
import dtrs.corba.utilities.DtrsConstants;



/**
 * This class is the implemetation object for your IDL interface.
 *
 * Let the Eclipse complete operations code by choosing 'Add unimplemented methods'.
 */
public class DTRSServerServant extends MyBoxOfficeServer.MyServicePOA implements Runnable{

	//Using this structure not sincronized we can locks a row of the map
	private HashMap<String,ShowDTO> boxOfficeInfoMemory =
		new HashMap<String,ShowDTO>();


	private ORB orb;
	private Hashtable<String,ServerLocation> serverPorts;


	// identify the name of the boxOffice
	private String nameServer; 

	//instance of object with synchronised methods
	private DtrsMapShowID mapShowId ;


	//Instance object ShowDTo to return in case the thread call the Map
	public ShowDTO genShowDtoMap;



	//instance to reserve file with synchronised methods
	private ReserveFile reserveFile = new ReserveFile();
	
	private boolean successOperation = false;


	/**
	 * Constructor for DTRSServerServant 
	 */
	public DTRSServerServant(String idBoxOffice, Hashtable<String,ServerLocation> serverPorts) {
		this.initBoxOffice(idBoxOffice);
		this.setServerPorts(serverPorts);
		//this.runConnectionUDP(idBoxOffice);
		
		
	}

	public void setORB(ORB orb_val) {
		orb = orb_val;
	}




	/**
	 * Base in the id box Office the map is created and the variable
	 * nameServer is created with the box Office ID
	 * @param idBoxOfficeP
	 */
	private void initBoxOffice(String idBoxOfficeP){
		boxOfficeInfoMemory.clear();

		if(idBoxOfficeP.equalsIgnoreCase(DtrsConstants.BOX_MONTREAL)){
			this.setIniMapInMemoryMtl();
			nameServer = DtrsConstants.BOX_MONTREAL;
		}else if(idBoxOfficeP.equalsIgnoreCase(DtrsConstants.BOX_TORONTO)){
			this.setIniMapInMemoryTor();
			nameServer = DtrsConstants.BOX_TORONTO;
		}else if(idBoxOfficeP.equalsIgnoreCase(DtrsConstants.BOX_CALGARY)){
			this.setIniMapInMemoryCal();
			nameServer = DtrsConstants.BOX_CALGARY;
		}

		mapShowId = new DtrsMapShowID(boxOfficeInfoMemory);

	}




	//------------------------------------------------------------------------------------

	/**
	 * Init the map to server Montreal with the showId, ticketAvailables, ticketsLeft
	 * In this case ticketAvailables = ticketsLeft
	 * @param idBoxOfficeP
	 */
	private void setIniMapInMemoryMtl(){


		boxOfficeInfoMemory.put("001",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"001", new Integer(150), new Integer(150)));

		boxOfficeInfoMemory.put("002",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"002", new Integer(120),new Integer(120)));

		boxOfficeInfoMemory.put("003",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"003", new Integer(100),new Integer(100)));

		boxOfficeInfoMemory.put("004",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"004", new Integer(80), new Integer(80)));

		boxOfficeInfoMemory.put("005",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"005", new Integer(50), new Integer(50)));

		boxOfficeInfoMemory.put("006",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"006", new Integer(60), new Integer(60)));

		boxOfficeInfoMemory.put("007",new ShowDTO(DtrsConstants.BOX_MONTREAL, 
				"007", new Integer(70), new Integer(70)));


	}
	/**
	 * Init the map to server TORONTO with the showId, ticketAvailables, ticketsLeft
	 * In this case ticketAvailables = ticketsLeft
	 * @param idBoxOfficeP
	 */
	private void setIniMapInMemoryTor(){


		boxOfficeInfoMemory.put("100",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"100", new Integer(90), new Integer(90)));

		boxOfficeInfoMemory.put("110",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"110", new Integer(70),new Integer(70)));

		boxOfficeInfoMemory.put("120",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"120", new Integer(50),new Integer(50)));

		boxOfficeInfoMemory.put("130",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"130", new Integer(55), new Integer(55)));

		boxOfficeInfoMemory.put("140",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"140", new Integer(120), new Integer(120)));

		boxOfficeInfoMemory.put("150",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"150", new Integer(130), new Integer(130)));

		boxOfficeInfoMemory.put("160",new ShowDTO(DtrsConstants.BOX_TORONTO, 
				"160", new Integer(140), new Integer(140)));


	}
	
	
	/**
	 * Init the map to server EDMONTON with the showId, ticketAvailables, ticketsLeft
	 * In this case ticketAvailables = ticketsLeft
	 * @param idBoxOfficeP
	 */
	private void setIniMapInMemoryCal(){


		boxOfficeInfoMemory.put("100",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"100", new Integer(90), new Integer(90)));

		boxOfficeInfoMemory.put("110",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"110", new Integer(70),new Integer(70)));

		boxOfficeInfoMemory.put("120",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"120", new Integer(50),new Integer(50)));

		boxOfficeInfoMemory.put("130",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"130", new Integer(55), new Integer(55)));

		boxOfficeInfoMemory.put("140",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"140", new Integer(120), new Integer(120)));

		boxOfficeInfoMemory.put("150",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"150", new Integer(130), new Integer(130)));

		boxOfficeInfoMemory.put("160",new ShowDTO(DtrsConstants.BOX_CALGARY, 
				"160", new Integer(140), new Integer(140)));


	}


	

	

	

	

	@Override
	public int check(String showID) {
		//comes like MON001
		ShowDTO showDto = new ShowDTO();
		//I need the 001
		String tt = showID.substring(3, 6);
		showDto.setShowID(showID.substring(3,6));

		runCheckThread(showDto);

		return genShowDtoMap.getNumberOfTicketsLeft();
	}

	
	@Override
	public String getHostname(){
		return nameServer;
		
	}


	private void runCheckThread(ShowDTO showDtoP){		

		Thread threadCheckTickets = new ManageMapShowSemaphore(mapShowId, showDtoP.getShowID(), null, 
				DtrsConstants.ROW_ACTION_GET_TICKETS_AVAILABLES, showDtoP);

		threadCheckTickets.start();	

		while(threadCheckTickets.isAlive()){
			showDtoP = ((ManageMapShowSemaphore)threadCheckTickets).getShowi();
			genShowDtoMap = showDtoP;
		}

	}


	@Override	
	public void reserve(String customerIdP, String showIdP, int numTicketsP) {		

		ShowDTO showDtoR = new ShowDTO();
		showDtoR.setShowID(showIdP);
		runReserveThread(Integer.valueOf(customerIdP), showDtoR, numTicketsP);

		//after running thread the object showObj by reference is updated with numTicketsLeft if
		// in case the reserve operation was not ok.in order to send as inout in Idl

		if(showDtoR.getNumberOfTicketsLeft() !=null ){
			//IntHolder intTicketServer = new IntHolder ((showObj.getNumberOfTicketsLeft()).intValue());
			numTicketsP = (showDtoR.getNumberOfTicketsLeft()).intValue();
		}


		//the return is a boolean also updated running the thread
		//return showObj.isSuccessOper();
		//successOperation = showDtoR.isSuccessOper();
		if(!showDtoR.isSuccessOper()){
			throw new IllegalArgumentException("Error in reserve operation");
		}

	}


	private void runReserveThread(int customerIdP, ShowDTO showiP, int numberOfTicketsP){		

		Thread threadReserveTickets = new ManageMapShowSemaphore(mapShowId, showiP.getShowID(),
				numberOfTicketsP,
				DtrsConstants.ROW_ACTION_UPDATE_RESERVE_TICKETS,
				showiP);		
		threadReserveTickets.start();

		while(threadReserveTickets.isAlive()){
			showiP = ((ManageMapShowSemaphore)threadReserveTickets).getShowi();
		}

		if(showiP.isSuccessOper()){	// the update in the map in memory wa ok.	

			//update or create  the file
			reserveFile.setCustomerIdinFile(customerIdP);
			reserveFile.setFileName(nameServer + showiP.getShowID());
			reserveFile.setNumberOfTickets(numberOfTicketsP);
			Thread threadF = new ManageFileSemaphore(reserveFile, DtrsConstants.OPERATION_RESERVE);
			threadF.start();
		}

	}



	@Override
	public void cancel(String customerIdP, String showIdP, int numTicketsP) {

		ShowDTO showDtoC = new ShowDTO();
		showDtoC.setShowID(showIdP);
		showDtoC.setNameOffice(nameServer);

		Integer ticketsAlreadyReserved = getQuantityTicketsReservedByClient(Integer.valueOf(customerIdP), 
				showDtoC);
		if(ticketsAlreadyReserved > 0 && numTicketsP <= ticketsAlreadyReserved ){
			runCancelThread(Integer.valueOf(customerIdP), showDtoC, numTicketsP);

			//after running thread the object showObj by reference is updated with numTicketsLeft if
			// in case the reserve operation was not ok.in order to send as inout in Idl

			if(showDtoC.getNumberOfTicketsLeft() !=null ){
				//IntHolder intTicketServer = new IntHolder ((showObj.getNumberOfTicketsLeft()).intValue());
				numTicketsP = (showDtoC.getNumberOfTicketsLeft()).intValue();
			}
		}else if(ticketsAlreadyReserved > 0 &&  numTicketsP > ticketsAlreadyReserved ){
			numTicketsP = 999;
			throw new IllegalArgumentException("The number of tickets to cancel is bigger than the tickets reserved");

		}else{
			numTicketsP = 0; //means the cliens cannot cancel because don't have any resrved
			throw new IllegalArgumentException("Upss! you don't have tickets in this showID");
			
		}

		//the return is a boolean also updated running the thread
		//successOperation = showDtoC.isSuccessOper();
	}




	private void runCancelThread(int customerIdP, ShowDTO showiP, int numberOfTicketsP){		

		Thread threadCancelTickets = new ManageMapShowSemaphore(mapShowId, showiP.getShowID(), 
				numberOfTicketsP,
				DtrsConstants.ROW_ACTION_UPDATE_CANCEL_TICKETS, showiP);	

		threadCancelTickets.start();


		while(threadCancelTickets.isAlive()){
			showiP = ((ManageMapShowSemaphore)threadCancelTickets).getShowi();

		}


		if(showiP.isSuccessOper()){
			//update or create  the file
			reserveFile.setCustomerIdinFile(customerIdP);
			reserveFile.setFileName(nameServer + showiP.getShowID());
			reserveFile.setNumberOfTickets(numberOfTicketsP);
			Thread thread = new ManageFileSemaphore(reserveFile,DtrsConstants.OPERATION_CANCEL_TICKETS);
			thread.start();
		}
	}


	/**
	 * Verify in the file the total of tickets already reserved by a client
	 */
	public Integer getQuantityTicketsReservedByClient(int customerID, ShowDTO showIDObj){

		runVerifyTicketsReservedThread(customerID, showIDObj);

		//return the value setted inthe object SHowDTO by reference	
		return showIDObj.getNumberOfTickets();

	}


	private void runVerifyTicketsReservedThread(int customerID, ShowDTO showIDObj){

		reserveFile.setCustomerIdinFile(customerID);
		reserveFile.setFileName(showIDObj.getNameOffice()+showIDObj.getShowID());

		Thread threadVerQtyCli = new ManageFileSemaphore(reserveFile, 
				DtrsConstants.OPERATION_CHECK_TICKETS_DISPO_BY_CUSTOMER,
				showIDObj);
		threadVerQtyCli.start();

		while(threadVerQtyCli.isAlive()){
			showIDObj = ((ManageFileSemaphore)threadVerQtyCli).getShowiFile();
		}

	}



	@Override
	public void exchange(String customerIdP, String showIdOri, int numTicketsOri, String showIdDes, int numTicketsDes) {


		ShowDTO showDtoEx = new ShowDTO();
		showDtoEx.setShowID(showIdOri);
		showDtoEx.setNameOffice(nameServer);

		//preserve atomicity

		//verify if customer has tickets reserved
		Integer ticketsAlreadyReserved = getQuantityTicketsReservedByClient(Integer.valueOf(customerIdP), 
				showDtoEx);

		if(ticketsAlreadyReserved > 0 && numTicketsOri <= ticketsAlreadyReserved ){

			//communicate with other server to check availables and Reserve the tickets

			String resultCallUDP = callUDPOtherServer(Integer.valueOf(customerIdP), showIdOri, showIdDes,
					numTicketsDes, DtrsConstants.CODE_OPERATION_UDP_RESERVE);

			if(!resultCallUDP.equals("") && 
					resultCallUDP.substring(0, 5).equals(DtrsConstants.COD_RESP_UDP_SUCCESS)){
				
				//if reserve was ok then cancel tickets to complete exchange operation
				synchronized(mapShowId.getMapShows().get(Integer.valueOf(showIdOri))){
					
					//IntHolder numTicketsPHolderCancel = new IntHolder(numTicketsOri);
					
					cancel(String.valueOf(customerIdP), showIdOri, numTicketsOri);
					
					if(!successOperation){

						//rollBack reserver operation
						resultCallUDP = DtrsConstants.COD_RESP_UDP_ERROR;

						while(resultCallUDP.equals(DtrsConstants.COD_RESP_UDP_ERROR)){

							resultCallUDP = callUDPOtherServer(Integer.valueOf(customerIdP), showIdOri, showIdDes,
									numTicketsDes, DtrsConstants.CODE_OPERATION_UDP_CANCEL);

						}
						//codRespo.value =  1002; //rollback in reserve operation in server desired
						showDtoEx.setSuccessOper(false);
						throw new IllegalArgumentException("There was an error in the operation. The exchanged operation was cancelled");
					}
					showDtoEx.setSuccessOper(true);//all was ok
				}



			}else{//the reserve operation has errors
				if(!resultCallUDP.equals("")){
					//codRespo.value = Integer.valueOf(resultCallUDP.substring(5));
					//showDtoEx.setSuccessOper(false);
					int codRespo = Integer.valueOf(resultCallUDP.substring(5));
					if (codRespo==0){
					 throw new IllegalArgumentException("Sorry!!! There are not tickets availables in the desired ShowId");
					}
				}else {
					//codRespo.value = 9999;//error loading other server
					//showDtoEx.setSuccessOper(false);
					throw new IllegalArgumentException("Sorry!!! We have some problems in the server");
					
				}
			}

		}else if(ticketsAlreadyReserved > 0 &&  numTicketsOri > ticketsAlreadyReserved ){
			//codRespo.value = 1000; // tickets to cancel are bigger that ticketsReserved
			//showDtoEx.setSuccessOper(false);
			throw new IllegalArgumentException("ERROR:The number of tickets to cancel is bigger than the tickets reserved");

		}else{
			//codRespo.value = 1001; //means the client cannot cancel because don't have any resrved
			showDtoEx.setSuccessOper(false);
			throw new IllegalArgumentException("Upss! you don't have tickets in reserved showID");
		}

		//the return is a boolean also updated running the thread
		//return showObj.isSuccessOper();		
		//successOperation = showDtoEx.isSuccessOper();
	}



	private String callUDPOtherServer(int customerIdP, String showIdOri,
			String showIdDes, int numTicketsDes, String operationP){

		String aMessage = "";
		DatagramSocket aSocket = null;
		String result="";
		try{
			//i NEED ONTAIN THE NAME SERVER BASE ON THE SHOWID
			
			

			// marshalling the content of the request for UDP communication between servers.	
			// operation code pos 0	
			//String showIdDesired = Integer.toString(showIdDes.getShowId()); //pos 1-4
			String showIdDesired = showIdDes;
			String customerIdExc = Integer.toString(customerIdP);//pos 4-9
			String numTicketsDesired = Integer.toString(numTicketsDes);//pos 10
			
			if(numTicketsDesired.length() < 3){
				for(int x = 0; x <= 3-numTicketsDesired.length(); x++){
					numTicketsDesired = "0" + numTicketsDesired;
				}

			}

			//place data in a byte array
			aMessage= operationP + showIdDesired + customerIdExc + numTicketsDesired;
			byte [] m = aMessage.getBytes();
			
			//create a datagram socket and bind it  to local port for the server desired
			aSocket = new DatagramSocket();
			
			//get the IP adress (InternetAdress) 

			String officeName = showIdDes.substring(0,3); 
			InetAddress aHost = InetAddress.getByName(DtrsConstants.URL_GENERAL_LOCALHOST);
			int serverPort = Integer.parseInt((serverPorts.get(officeName)).getPort());

			//create the packets			
			//create adatagram packet, specifying the data array and the receiver's address
			DatagramPacket request = new DatagramPacket(m, aMessage.length(), aHost, serverPort);
			
			//invoke the send method of the socket with a reference to the datagram packet;
			aSocket.send(request);
			byte[] buffer = new byte[1000];
			
			
			DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
			
			
			aSocket.receive(reply);

			result = new String(reply.getData()).trim();

		}catch (SocketException e){System.out.println("Socket: " + e.getMessage());}
		catch (IOException e){System.out.println("IO: " + e.getMessage());}
		catch (Exception ex){System.out.println("Exchanged Request Error\n"+ex.getMessage());}
		finally {if(aSocket != null) aSocket.close();} 

		return result;


	}


	public void setServerPorts(Hashtable<String,ServerLocation> serverPorts) {
		this.serverPorts = serverPorts;
	}




	public void run() {
		DatagramSocket aSocket = null;
		String aReply,aRequest;
		

		try{
			
			//create adatagram socket and bind it to a specific local port
			aSocket = new DatagramSocket(Integer.parseInt((serverPorts.get(nameServer)).getPort()));
			byte[] buffer = new byte[1000];

			while(true){

				aReply = DtrsConstants.COD_RESP_UDP_SUCCESS;
				
				//create adatagram packet  specifying the data array
				DatagramPacket request = new DatagramPacket(buffer, buffer.length);
				//invoke the receive method of the socket with a reference to thedatagram packet;
				aSocket.receive(request);

				//create a byte array for receiving the data
				aRequest = new String(request.getData());
				String codeOperation = aRequest.substring(0, 1);
				String showID = aRequest.substring(1, 4);
				Integer customerId = Integer.parseInt(aRequest.substring(4, 10));				
				//IntHolder ticketsDesired = new IntHolder(Integer.valueOf(aRequest.substring(10,13)));
				int ticketsDesired = (Integer.valueOf(aRequest.substring(10,13)));
				
				ShowDTO showDtoRun = new ShowDTO();
				showDtoRun.setShowID(showID);


				synchronized(mapShowId.getMapShows().get(Integer.valueOf(showID))){					



					if(codeOperation.equals(DtrsConstants.CODE_OPERATION_UDP_RESERVE)){						

						Integer ticketsAvailables = check(showID);							
						if(ticketsAvailables > 0 ){
							
							reserve(customerId.toString(), showID, ticketsDesired);
							
							if(successOperation){
							aReply = DtrsConstants.COD_RESP_UDP_SUCCESS;}
							
						}
						//the variable ticketsDesired is passes by reference so we can have the reponse in
						//case of reserve

						if(ticketsAvailables == 0)	{					
							aReply = DtrsConstants.COD_RESP_UDP_ERROR + String.valueOf(ticketsAvailables);
						}else if (ticketsDesired == 0 
								|| ticketsDesired == 999){
							aReply = DtrsConstants.COD_RESP_UDP_ERROR + String.valueOf(ticketsDesired);
						}

					}else if(codeOperation.equals(DtrsConstants.CODE_OPERATION_UDP_CANCEL)){
						
						
						cancel(String.valueOf(customerId), showID, ticketsDesired);
						
						if(!successOperation)
						{
							aReply = DtrsConstants.COD_RESP_UDP_ERROR;
						}
					}


				}

				byte [] m = aReply.getBytes();
				DatagramPacket reply = new DatagramPacket(m, aReply.length(), request.getAddress(), request.getPort());
				aSocket.send(reply);
			}


		}catch (SocketException e){System.out.println("Socket: " + e.getMessage());
		}catch (IOException e) {System.out.println("IO: " + e.getMessage());}
		finally {
			if(aSocket != null) aSocket.close();
		}


	}



}
